﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web;
using System.Net;
using System.Threading;

namespace Laharsub.Client.Net.Test
{
    [TestClass]
    public class PubsubClientTest
    {
        static TimeSpan defaultTimeout = TimeSpan.FromSeconds(15); // you may want to increase this when debugging
        //static TimeSpan defaultTimeout = TimeSpan.FromHours(2);
        string baseAddress = "http://localhost/ps/memory";

        void AssertException(Type ex, Action action)
        {
            try
            {
                action();
                Assert.Fail("Action did not result in an expected exception.");
            }
            catch (Exception e)
            {
                Assert.AreEqual(e.GetType(), ex);
            }
        }

        [TestMethod]
        [Description("Check preconditions for running unit tests: Laharsub service must be running.")]
        public void A1_EnsureLaharsubServerRunning()
        {
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    done.Set();
                },
                delegate(Exception ex)
                {
                    e = ex; done.Set();
                }
            );
            string error = "Laharsub server is not running. Laharsub server must be accessible at " + this.baseAddress + ". It is required for the Silverlight unit tests. Please run the server from the Visual Studio solution or from the command line using Laharsub.exe /console.";
            this.WaitOrTimeOut(done, error);
            Assert.IsNull(e, error);
        }

        [TestMethod]
        [Description("Invalid null as base address")]
        public void B2_ConstructorTest()
        {
            this.AssertException(typeof(ArgumentException), delegate() { new PubsubClient(null); });
        }

        [TestMethod]
        [Description("Invalid relative URI as base address")]
        public void B3_ConstructorTest()
        {
            this.AssertException(typeof(ArgumentException), delegate() { new PubsubClient("foo"); });
        }

        [TestMethod]
        [Description("Valid absolute URI as base address")]
        public void B4_ConstructorTest()
        {
            PubsubClient client = new PubsubClient("uri://foo");
            Assert.AreEqual(client.BaseAddress, new Uri("uri://foo/"));
        }

        void WaitOrTimeOut(WaitHandle hande, string error)
        {
            this.WaitOrTimeOut(hande, error, defaultTimeout);
        }

        void WaitOrTimeOut(WaitHandle hande, string error, TimeSpan timeout)
        {
            if (!hande.WaitOne(timeout))
            {
                Assert.Fail(error);
            }
        }

        [TestMethod]
        [Description("Create a topic.")]
        public void C_CreateTopic()
        {
            int tid = -1;
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done.Set();
                },
                delegate(Exception ex)
                {
                    e = ex; done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Create topic timed out");
            Assert.IsNull(e);
            Assert.IsTrue(tid > 0);
        }

        [TestMethod]
        [Description("Create a topic and publish to it.")]
        public void D_PublishOne()
        {
            int tid = -1;
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e = null;
            PubsubMessage message = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done.Set();
                },
                delegate(Exception ex)
                {
                    e = ex; done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Create topic has timed out");
            Assert.IsNull(e);
            Assert.IsTrue(tid > 0);
            done.Reset();
            client.PublishAsync(
                tid,
                "Hello D_PublishOne",
                delegate(PubsubMessage m)
                {
                    message = m;
                    done.Set();
                },
                delegate(PubsubMessage m, Exception ex)
                {
                    e = ex;
                    message = m;
                    done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Publish message has timed out");
            Assert.IsNull(e);
            Assert.IsNotNull(message);
            Assert.AreEqual(message.TopicId, tid);
            Assert.AreEqual(message.GetBodyAsString(), "Hello D_PublishOne");
            Assert.IsTrue(message.MessageId > 0);
            Assert.AreEqual(message.ContentType, "text/plain");
        }

        [TestMethod]
        [Description("Subscribe to a topic, publish a message, verify it is delivered.")]
        public void E1_SubscribePublishOne()
        {
            int tid = -1;
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e = null;
            PubsubMessage message = null;
            Subscription sub = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done.Set();
                },
                delegate(Exception ex)
                {
                    e = ex; done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Create topic has timed out");
            Assert.IsNull(e);
            Assert.IsTrue(tid > 0);
            done.Reset();
            client.SubscribeAsync(new Subscription
            {
                TopicId = tid,
                From = 1,
                OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                {
                    message = m;
                    sub = s;
                    done.Set();
                },
                OnError = delegate(Subscription s, Exception ex)
                {
                    sub = s;
                    e = ex;
                    done.Set();
                }
            });
            client.PublishAsync(tid, "Hello E1_SubscribePublishOne");
            this.WaitOrTimeOut(done, "Notification has not been received within the alloted time");
            Assert.IsNotNull(sub);
            Assert.IsNull(e);
            Assert.IsNotNull(message);
            Assert.AreEqual(message.TopicId, tid);
            Assert.AreEqual(message.GetBodyAsString(), "Hello E1_SubscribePublishOne");
            Assert.IsTrue(message.MessageId > 0);
            Assert.AreEqual(message.ContentType, "text/plain");
            Assert.AreEqual(sub.TopicId, tid);
            Assert.IsTrue(sub.From > message.MessageId);
        }

        [TestMethod]
        [Description("Subscribe to one topic, publish three messages, verify all are received.")]
        public void E2_SubscribePublishThree()
        {
            int sum = 0;
            int tid = -1;
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done.Set();
                },
                delegate(Exception ex)
                {
                    e = ex; done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Create topic has timed out");
            Assert.IsNull(e);
            Assert.IsTrue(tid > 0);
            done.Reset();
            client.SubscribeAsync(new Subscription
            {
                TopicId = tid,
                From = 1,
                OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                {
                    sum += int.Parse(m.GetBodyAsString());
                    if (sum == 7) done.Set();
                },
                OnError = delegate(Subscription s, Exception ex)
                {
                    e = ex;
                    done.Set();
                }
            });
            client.PublishAsync(tid, "1");
            client.PublishAsync(tid, "2");
            client.PublishAsync(tid, "4");
            this.WaitOrTimeOut(done, "Notifications have not been received in the alloted time");
            Assert.IsNull(e);
            Assert.AreEqual(sum, 7, "Not all published messages were received");
        }

        [TestMethod]
        [Description("Subscribe to two topics, publish to one of them, verify the message is delivered to the right subscription.")]
        public void E3_SubscribeTwoPublishOne()
        {
            int tid1 = -1;
            int tid2 = -1;
            int counter = 0;
            ManualResetEvent done = new ManualResetEvent(false);
            Exception e1 = null;
            Exception e2 = null;
            PubsubMessage message1 = null;
            PubsubMessage message2 = null;
            Subscription sub1 = null;
            Subscription sub2 = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid1 = topicId;
                    if (Interlocked.Increment(ref counter) == 2) done.Set();
                },
                delegate(Exception ex)
                {
                    e1 = ex;
                    if (Interlocked.Increment(ref counter) == 2) done.Set();
                }
            );
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid2 = topicId;
                    if (Interlocked.Increment(ref counter) == 2) done.Set();
                },
                delegate(Exception ex)
                {
                    e2 = ex;
                    if (Interlocked.Increment(ref counter) == 2) done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Topics have not been created in the alloted time");
            Assert.IsNull(e1);
            Assert.IsTrue(tid1 > 0);
            Assert.IsNull(e2);
            Assert.IsTrue(tid2 > 0);
            done.Reset();
            client.SubscribeAsync(new Subscription
            {
                TopicId = tid1,
                From = 1,
                OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                {
                    message1 = m;
                    sub1 = s;
                    done.Set();
                },
                OnError = delegate(Subscription s, Exception ex)
                {
                    sub1 = s;
                    e1 = ex;
                    done.Set();
                }
            });
            client.SubscribeAsync(new Subscription
            {
                TopicId = tid2,
                From = 1,
                OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                {
                    message2 = m;
                    sub2 = s;
                    done.Set();
                },
                OnError = delegate(Subscription s, Exception ex)
                {
                    sub2 = s;
                    e2 = ex;
                    done.Set();
                }
            });
            client.PublishAsync(tid1, "Hello E3_SubscribeTwoPublishOne");
            this.WaitOrTimeOut(done, "Notification was not received in the alloted time");
            Assert.IsNotNull(sub1);
            Assert.IsNull(e1);
            Assert.IsNotNull(message1);
            Assert.AreEqual(message1.TopicId, tid1);
            Assert.AreEqual(message1.GetBodyAsString(), "Hello E3_SubscribeTwoPublishOne");
            Assert.IsTrue(message1.MessageId > 0);
            Assert.AreEqual(message1.ContentType, "text/plain");
            Assert.AreEqual(sub1.TopicId, tid1);
            Assert.IsTrue(sub1.From > message1.MessageId);
            Assert.IsNull(sub2);
            Assert.IsNull(e2);
            Assert.IsNull(message2);
        }

        [TestMethod]
        [Description("Subscribe and cancel subscription, then publish - the message should never be received.")]
        public void F1_SubscribeCancel()
        {
            int tid1 = -1;
            Exception e1 = null;
            PubsubMessage message1 = null;
            Subscription sub1 = null;
            PubsubClient client = new PubsubClient(this.baseAddress);
            ManualResetEvent done = new ManualResetEvent(false);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid1 = topicId; done.Set();
                },
                delegate(Exception ex)
                {
                    e1 = ex; done.Set();
                }
            );
            this.WaitOrTimeOut(done, "Create topic has timed out");
            Assert.IsNull(e1);
            Assert.IsTrue(tid1 > 0);
            done.Reset();
            client.SubscribeAsync(new Subscription
            {
                TopicId = tid1,
                From = 1,
                OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                {
                    message1 = m;
                    sub1 = s;
                    done.Set();
                },
                OnError = delegate(Subscription s, Exception ex)
                {
                    sub1 = s;
                    e1 = ex;
                    done.Set();
                }
            });
            client.Unsubscribe(tid1);
            client.PublishAsync(tid1, "Hello F1_SubscribeCancel");
            Assert.IsFalse(done.WaitOne(TimeSpan.FromSeconds(3)), "Notification was received after subscription was cancelled");
            Assert.IsNull(e1);
            Assert.IsNull(message1);
            Assert.IsNull(sub1);
        }
    }
}
